React+Redux 单界面应用

之前的项目需要做一个后端管理的web,既然要做,就挑战一下自己吧,打算使用react+redux 做一个单界面的应用。为什么要用这个组合去做呢,React我就不解释了,这是最近非常火的一个前端框架,但是他还是有一些缺点的,比如他在各组件之间的数据交互相对来说比较麻烦,而Redux的作用就是用来管理数据流,管理状态,所以选择用React和Redux结合起来做。

这里还要使用一些其他的框架,使用 react-redux 去连接React和Redux;使用 react-routerreact-router-redux 去做前端路由;使用 redux-thunk 去支持异步action等。

下面了解一下Redux的数据流(按顺序进行):

首先触发action:action 可以理解为应用向 store 传递的数据信息(一般为用户交互信息)。在实际应用中,传递的信息可以约定一个固定的数据格式,简单形式如下

1
2
3
function getCourse(data) {
return {type: 'Get_Courses', data};
}

dispatch(action):这是一个同步的过程:执行 reducer 更新 state -> 调用 store 的监听处理函数。如果需要在 dispatch 时执行一些异步操作可以通过引入 Middleware(redux-thunk) 解决。

最后reducer 改变状态:reducer的作用就是根据现在的state和action来得到新的state,也就是说这有在这一步state树才会被改变。

下面结合我的代码去说明:(这里以获取课程信息为例)

首先是前端目录结构:

1
2
3
4
5
6
7
8
9
10
11
admin/
├── actions/ 用于存放action文件
│ └── course.js
├── components/ 能够复用的React组件
├── containers/ 界面
| └── course.js
├── reducers/ 存放reducers处理文件
| └── course.js
├── store/ 将reducer整合在一起
| └── rootStore.js
└── index.js 入口文件

action/course.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getCourse(data) {
return {type: 'Get_Courses', data};
}
export function getCoursesAction(data) {
return dispatch=> {
fetch(`/admin/course/list?search=${data.search}&page=${data. page}`, {credentials: 'include'})
.then((response) => response.text())
.then((responseText) => {
var data = JSON.parse(responseText);
data.data = JSON.parse(data.data)
dispatch(getCourse(data))
})
.catch((error) => {
console.warn(error);
});
}
}

因为action函数是不支持异步的,所以这里将异步操作放到getCoursesAction函数里面,当异步获取成功数据之后通过dispacth调用getCourse函数将数据提交给reducer处理。


reducers/course.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var defaultFields = {
data: [],
total:0,
page:1,
count:10,
search: '',
};
export function courses(state = defaultFields, action) {
switch (action.type) {
case 'Get_Courses':
return action.data
default:
return state;
}
}

其实这里的参数action就是我们在action/course.js中返回的数据

1
return {type: 'Get_Courses', data};

所以我们通过action.type获取我们约定的操作,这里我们如果type是“Get_Courses”就会根据action里面的data更新state。


store/rootStore.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createStore, applyMiddleware,compose ,combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux';
import thunk from 'redux-thunk';
import * as course from '../reducers/course.js';
//将拆分的reducer组合起来
const reducer = combineReducers({
...course,
routing: routerReducer
});
//添加中间件
const createStoreWithMiddleware = compose(
applyMiddleware(
thunk
),
window.devToolsExtension ? window.devToolsExtension() : f => f
)(createStore)
//生成store
export default createStoreWithMiddleware(reducer)

containers/course.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mport React, { Component } from 'react';
import { connect } from 'react-redux';
class Course extends React.Component {
...
render() {
//获取到courses数据
var data = this.props.data;
return(...);
}
}
//将store中的course信息绑定到Course组件的props上
function show(state) {
return {
data: state.courses,
fields:state.fields
};
}
export default connect(show)(Course);


index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createStore, applyMiddleware,compose ,combineReducers} from 'redux';
import { Provider } from 'react-redux';
import Share from './components/share.js';
import User from './containers/user.js';
import Field from './containers/field.js';
import Course from './containers/course.js';
import Section from './containers/section.js';
import Lesson from './containers/lesson.js';
import store from './store/rootStore.js';
import {routerReducer, syncHistoryWithStore } from 'react-router-redux';
import { Route, Router ,browserHistory, IndexRoute ,hashHistory } from 'react-router';
import {createHistory } from 'history'
let history = syncHistoryWithStore(hashHistory, store)
let rootElement = document.getElementById('contain');
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Share}>
<Route path="user" component={User}/>
<Route path="field" component={Field}/>
<Route path="course" component={Course}/>
<Route path="section/:id" component={Section}/>
<Route path="lesson/:id" component={Lesson}/>
</Route>
</Router>
</Provider>,
rootElement
);

index.js的作用主要是把所有的东西连在一起,这里使用了react-router中的 syncHistoryWithStore 函数去做前端路由,这样我的单页面应用就建起来了.